#include "parser.h"
#include "gnum.h"

namespace cnc {

	namespace compiler {

		Parser::Parser() : programComponent_(PROGRAM_START),
			scanner_(0), programOverride_(0), er_(0)
		{
		}

		void Parser::setErrorReporter(ErrorReporter *er)
		{
			er_ = er;
		}

		void Parser::reset()
		{
			programComponent_ = PROGRAM_START;
		}

		void Parser::setScanner(Scanner *scanner)
		{
			scanner_ = scanner;
		}

		void Parser::setProgramOverride(ProgramOverride *programOverride)
		{
			programOverride_ = programOverride;
		}

		ProgramComponentID Parser::getProgramComponent()
		{
			if (programComponent_ == PROGRAM_START) {
				programComponent_ = PROGRAM_LEADER;
				return PROGRAM_START;
			}
			return programComponent_;
		}

		const char *Parser::getLeader()
		{
			programComponent_ = PROGRAM_NUMBER;
			return scanner_->getLeader();
		}

		unsigned long Parser::getProgramNumber()
		{
			enum {
				START, GOT_ADDRESS, GOT_NUMBER, ACCEPT_NUMBER,
				IGNORE_SPACES, ERROR
			} state = START;
			unsigned long number = 0;

			while ("no program number is accepted")
				switch (state) {
				case START:
					// exit
					lookahead_ = scanner_->getToken();
					switch (lookahead_.getID()) {
					case TOKEN_WHITE_SPACE:
					case TOKEN_COMMENT:
					case TOKEN_EOL:
						state = START;
						break;
					case TOKEN_PROGRAM_NUMBER:
						state = GOT_ADDRESS;
						break;
					default:
						state = ERROR;
					}
					break;
				case GOT_ADDRESS:
					// exit
					lookahead_ = scanner_->getToken();
					switch (lookahead_.getID()) {
					case TOKEN_UINT:
						state = GOT_NUMBER;
						break;
					default:
						state = ERROR;
					}				
					break;
				case GOT_NUMBER:
					// entry
					{
						GCodeNumber gCodeNumber(lookahead_.getValue());
						number = gCodeNumber.getSignificand();
					}
				case IGNORE_SPACES:
					// exit
					lookahead_ = scanner_->getToken();
					switch (lookahead_.getID()) {
					case TOKEN_WHITE_SPACE:
					case TOKEN_COMMENT:
						state = IGNORE_SPACES;
						break;
					case TOKEN_EOL:
					case TOKEN_EOF:
						state = ACCEPT_NUMBER;
						break;
					default:
						state = ERROR;
					}
					break;
				case ACCEPT_NUMBER:
					programComponent_ = PROGRAM_BLOCK;
					return number;
					break;
				case ERROR:
					programComponent_ = PROGRAM_ERROR;
					return 0;
				}
		}

		Block Parser::getBlock()
		{
			enum {
				START, ACCEPT_NO_BLOCK, ACCEPT_EMPTY_BLOCK,
				IGNORE_BLOCK, ACCEPT_SKIPPED_BLOCK,
				GOT_ADDRESS, GOT_NUMBER, ACCEPT_BLOCK,
				BLOCK_EXPECTED, ADDRESS_EXPECTED, NUMBER_EXPECTED,
				EOL_UNEXPECTED, TOO_MANY_DIGITS
			} state = START;
			Block block;
			char address;
			long int value;

			while ("no block is accepted") {
				switch (state) {
				case BLOCK_EXPECTED:
					// entry
					er_->report(ERRORCLS_ERROR, ERRORMSG_BLOCK_EXPECTED,
						"", scanner_->getRow(), scanner_->getColumn());
				case START:
					// exit
					lookahead_ = scanner_->getToken();
					switch (lookahead_.getID()) {
					case TOKEN_WHITE_SPACE:
					case TOKEN_COMMENT:
						state = START;
						break;
					case TOKEN_EOF:
						state = ACCEPT_NO_BLOCK;
						break;
					case TOKEN_EOL:
						state = ACCEPT_EMPTY_BLOCK;
						break;
					case TOKEN_BLOCK_DELETE:
						if (programOverride_->getBlockDelete() == true)
							state = IGNORE_BLOCK;
						break;
					case TOKEN_ADDRESS:
						state = GOT_ADDRESS;
						break;
					default:
						state = BLOCK_EXPECTED;
					}
					break;
				case ACCEPT_NO_BLOCK:
					// entry
					block.setID(BLOCK_NONE);
					programComponent_ = PROGRAM_END;
					// exit
					return block;
				case ACCEPT_EMPTY_BLOCK:
					// entry
					block.setID(BLOCK_EMPTY);
					// exit
					return block;
				case IGNORE_BLOCK:
					// exit
					lookahead_ = scanner_->getToken();
					if (lookahead_.getID() == TOKEN_EOL
						|| lookahead_.getID() == TOKEN_EOF)
						state = ACCEPT_SKIPPED_BLOCK;
					break;
				case ACCEPT_SKIPPED_BLOCK:
					// entry
					block.setID(BLOCK_SKIPPED);
					// exit
					return block;
				case NUMBER_EXPECTED:
					// entry
					er_->report(ERRORCLS_ERROR, ERRORMSG_NUMBER_EXPECTED,
						"", scanner_->getRow(), scanner_->getColumn());
				case GOT_ADDRESS:
					// entry
					address = (char)lookahead_.getValue();
					// exit
					lookahead_ = scanner_->getToken();
					switch (lookahead_.getID()) {
					case TOKEN_UINT:
					case TOKEN_NINT:
					case TOKEN_UFLOAT:
					case TOKEN_NFLOAT:
						if (lookahead_.getErrorCode() == ERROR_NONE)
							state = GOT_NUMBER;
						else
							state = TOO_MANY_DIGITS;
						break;
					case TOKEN_EOL:
					case TOKEN_EOF:
						state = EOL_UNEXPECTED;
						break;
					default:
						state = NUMBER_EXPECTED;
						break;
					}
					break;
				case TOO_MANY_DIGITS:
					// entry
					er_->report(ERRORCLS_WARNING, ERRORMSG_TOO_MANY_DIGITS,
						"", scanner_->getRow(), scanner_->getColumn());
					// exit
					state = GOT_NUMBER;
					break;
				case ADDRESS_EXPECTED:
					// entry
					er_->report(ERRORCLS_ERROR, ERRORMSG_ADDRESS_EXPECTED,
						"", scanner_->getRow(), scanner_->getColumn());
				case GOT_NUMBER:
					// entry
					value = (unsigned long)lookahead_.getValue();
					block.addWord(address, value);
					// exit
					lookahead_ = scanner_->getToken();
					switch (lookahead_.getID()) {
					case TOKEN_EOL:
					case TOKEN_EOF:
						state = ACCEPT_BLOCK;
						break;
					case TOKEN_WHITE_SPACE:
					case TOKEN_COMMENT:
						state = GOT_NUMBER;
						break;
					case TOKEN_ADDRESS:
						state = GOT_ADDRESS;
						break;
					default:
						state = ADDRESS_EXPECTED;
						break;
					}
					break;
				case EOL_UNEXPECTED:
					// entry
					er_->report(ERRORCLS_ERROR, ERRORMSG_EOL_UNEXPECTED,
						"", scanner_->getRow(), scanner_->getColumn());
				case ACCEPT_BLOCK:
					// entry
					block.setID(BLOCK_STANDARD);
					// exit
					return block;
				}
				if (er_->errorsExceeded() || er_->warningsExceeded()) {
					programComponent_ = PROGRAM_ERROR;
					return block;
				}
			}
		}

	} // namespace compiler

} // namespace cnc

